#include "stdafx.h"
#include "ProjectGenerator.h"
#include "MultiLinePropertyHandler.h"

CProjectGenerator::CProjectGenerator(void) :
	m_bIsModifyMainNeeded(FALSE),
	m_bIsAnsi(FALSE)
{
	CoCreateGuid(&m_guidProj);
}

CProjectGenerator::~CProjectGenerator(void)
{
}

BOOL CProjectGenerator::MakeTestProjectPath(LPCWSTR lpTemplatePath, LPCWSTR lpTestProjectName)
{
	CPathW csPath =lpTemplatePath;
	CAtlStringW strExtension = csPath.GetExtension();
	csPath.UnquoteSpaces();
	csPath.RemoveFileSpec();
	csPath.Append(L"UnitTest");
	csPath.Append(lpTestProjectName);

	int n = 0;
	csPath.AddExtension(strExtension);
	do
	{
		++n;
		if (csPath.FileExists())
		{
			csPath.RemoveFileSpec();
			csPath.m_strPath.AppendFormat(_T("\\%s%i%s"), lpTestProjectName, n, strExtension);
		} else {
			m_strTestProjectPath = csPath;
			return TRUE;
		}
	}
	while (n < 10000);

	return FALSE;
}

BOOL CProjectGenerator::MakeTestProject(LPCWSTR lpTemplatePath, LPCWSTR lpTestProjectName)
{
	VARIANT_BOOL vb;
	CComVariant vt;

	IXMLDOMDocumentPtr spDocument(CLSID_DOMDocument);
	IXMLDOMNodePtr spNode;
	IXMLDOMElementPtr spVisualStudioProject;
	CComBSTR bstrKeyword(L"Keyword");

	CComVariant vtTestProjectName(lpTestProjectName);

	if (MakeTestProjectPath(lpTemplatePath, lpTestProjectName) &&
		SUCCEEDED(spDocument->load(CComVariant(lpTemplatePath), &vb)) &&
		SUCCEEDED(spDocument->selectSingleNode(CComBSTR(L"VisualStudioProject"), &spNode)) &&
		(spVisualStudioProject = spNode) &&
		//set a new project name, a new guid and set type to Win32Proj	
		SUCCEEDED(spVisualStudioProject->setAttribute(CComBSTR(L"Name"), vtTestProjectName)) &&
		SUCCEEDED(spVisualStudioProject->setAttribute(CComBSTR(L"RootNamespace"), vtTestProjectName)) &&
		SUCCEEDED(spVisualStudioProject->setAttribute(CComBSTR(L"ProjectGUID"), CComVariant(m_guidProj))))
	{
		BOOL bIsConsole = FALSE;
		BOOL bIsWin32Proj = FALSE;

		if ((S_OK == spVisualStudioProject->getAttribute(bstrKeyword, &vt)) &&
			(FALSE == (bIsWin32Proj = (0 == wcscmp(vt.bstrVal, L"Win32Proj")))) &&
			(FAILED(spVisualStudioProject->setAttribute(bstrKeyword, CComVariant(L"Win32Proj")))))
				return FALSE;

		if (FALSE == ChangeCompilerConfiguration(spVisualStudioProject, bIsConsole))
			return FALSE;

		m_bIsModifyMainNeeded = bIsWin32Proj && bIsConsole;

		//remove source control binding if any
		spVisualStudioProject->removeAttribute(CComBSTR(L"SccProjectName"));
		spVisualStudioProject->removeAttribute(CComBSTR(L"SccAuxPath"));
		spVisualStudioProject->removeAttribute(CComBSTR(L"SccLocalPath"));
		spVisualStudioProject->removeAttribute(CComBSTR(L"SccProvider"));

		CPath pathFolder = m_strTestProjectPath;
		pathFolder.RemoveFileSpec();
		CreateDirectory(pathFolder, NULL);

		RemoveHlpFiles(spVisualStudioProject);
		RemovePostBuildEvents(spVisualStudioProject);
		DefineOutputAndIntermediateDir(spVisualStudioProject);

		if (ModifyLinkerOptions(spVisualStudioProject) &&
			ModifyPaths(spVisualStudioProject) &&
			SUCCEEDED(spDocument->save(CComVariant(m_strTestProjectPath.m_strPath))))
		{
			return TRUE;
		}
	}
	return FALSE;
}
BOOL CProjectGenerator::ChangeCompilerConfiguration(IXMLDOMElementPtr spVisualStudioProject, BOOL & bIsConsole)
{
	IXMLDOMNodeListPtr spNodeList;
	IXMLDOMElementPtr spVCCLCompilerTool;
	IXMLDOMNodePtr spNode;
	CComVariant vt;

	long nCount = 0;
	if (SUCCEEDED(spVisualStudioProject->selectNodes(CComBSTR(L"Configurations/Configuration/Tool[@Name=\"VCCLCompilerTool\"]"), &spNodeList)) &&
		SUCCEEDED(spNodeList->get_length(&nCount)))
	{
		
		CComBSTR bstrPreprocessorDefinitions(L"PreprocessorDefinitions");
		CComBSTR bstrAdditionalIncludeDirectories(L"AdditionalIncludeDirectories");
		CAtlStringW wstr;

		for(long n = 0; n < nCount; ++n)
		{
			if (SUCCEEDED(spNodeList->get_item(n, &spNode)) &&
				(spVCCLCompilerTool = spNode))
			{
				//change precompile definition
				if (S_OK == spVCCLCompilerTool->getAttribute(bstrPreprocessorDefinitions, &vt))
				{
					wstr = vt.bstrVal;
					BOOL bWasConsole = FALSE;
					if (ChangePreprocessorDirectiveToConsole(wstr, bWasConsole))
					{
						if (FAILED(spVCCLCompilerTool->setAttribute(bstrPreprocessorDefinitions, CComVariant(wstr))))
							return FALSE; // no need to continue - at least one error
					}
					bIsConsole |= bWasConsole;
				}

				//change additional directory
				if (S_OK == spVCCLCompilerTool->getAttribute(bstrAdditionalIncludeDirectories, &vt))
					wstr = vt.bstrVal;
				else
					wstr.Empty();

				ModifyAdditionalIncludeDirectories(wstr);
				if (FAILED(spVCCLCompilerTool->setAttribute(bstrAdditionalIncludeDirectories, CComVariant(wstr))))
					return FALSE; // no need to continue - at least one error
			}
		}

		//define charset
		if (spVCCLCompilerTool)
		{	//only one configuration is used and ignore all setting in the FileConfiguration element
			if (S_OK == spVCCLCompilerTool->getAttribute(CComBSTR(L"CharacterSet"), &vt))
			{
				m_bIsAnsi = 0 == wcscmp(vt.bstrVal, L"0");
			}
		}

	}
	return TRUE;
}

BOOL CProjectGenerator::ChangePreprocessorDirectiveToConsole(IN OUT CAtlStringW & wstr, OUT BOOL & bWasConsole)
{
	CMultiLinePropertyHandler csPreprocessorDefinitionHandler(wstr);

	bWasConsole = csPreprocessorDefinitionHandler.IsDefined(L"_CONSOLE");
	
	csPreprocessorDefinitionHandler.RemoveIfExist(L"_WINDOW");
	csPreprocessorDefinitionHandler.RemoveIfExist(L"_USRDLL");
	csPreprocessorDefinitionHandler.RemoveIfExist(L"_LIB");

	csPreprocessorDefinitionHandler.Add(L"_CONSOLE");
	csPreprocessorDefinitionHandler.Add(L"_UT");

	csPreprocessorDefinitionHandler.GetPropertyLine(wstr);

	return csPreprocessorDefinitionHandler.IsModified(); 
}

void CProjectGenerator::ModifyAdditionalIncludeDirectories(IN OUT CAtlStringW & wstr)
{
	CMultiLinePropertyHandler csAdditionalIncludeDirectoriesHandler(wstr);

	size_t nCount = csAdditionalIncludeDirectoriesHandler.GetCount();
	for (size_t n = 0; n < nCount; ++n)
	{
		CAtlStringW wstr = csAdditionalIncludeDirectoriesHandler.GetAt(n);
		int nOffset = wstr[0] == L'"' ? 1 : 0;
		if (wstr[nOffset + 1] != L':')
		{	//path is not absolute
			wstr.Insert(nOffset, L"..\\");
			csAdditionalIncludeDirectoriesHandler.SetAt(n, wstr);
		}
	}

	csAdditionalIncludeDirectoriesHandler.Add(L"..");
	csAdditionalIncludeDirectoriesHandler.GetPropertyLine(wstr);
}

BOOL CProjectGenerator::ModifyLinkerOptions(IXMLDOMElementPtr & spVisualStudioProject)
{
	IXMLDOMNodeListPtr spNodeList;
	IXMLDOMNodePtr spNode;
	IXMLDOMElementPtr spElement;
	long nCount = 0;

	if (SUCCEEDED(spVisualStudioProject->selectNodes(CComBSTR(L"Configurations/Configuration/Tool[@Name=\"VCLinkerTool\"]"), &spNodeList)) &&
		SUCCEEDED(spNodeList->get_length(&nCount)))
	{
		CComVariant vt;
		static CComBSTR bstrOutputFile(L"OutputFile");
		static CComBSTR bstrSubSystem(L"SubSystem");
		static CComBSTR bstrGenerateDebugInformation(L"GenerateDebugInformation");
		static CComBSTR bstrTargetMachine(L"TargetMachine");
		static CComBSTR bstrModuleDefinitionFile(L"ModuleDefinitionFile");
		static CComBSTR bstrRegisterOutput(L"RegisterOutput");
		CComVariant vt1(1);
		CComVariant vtTrue(L"true");

		if (nCount)
		{
			for(long n = 0; n < nCount; ++n)
			{
				if (SUCCEEDED(spNodeList->get_item(n, &spNode)) &&
					(spElement = spNode))
				{
					if(S_OK == spElement->getAttribute(bstrOutputFile, &vt))
					{
						CPathW path = vt.bstrVal;
						path.UnquoteSpaces();
						path.RenameExtension(L".exe");
						spElement->setAttribute(bstrOutputFile, CComVariant(path.m_strPath));
					}
					spElement->setAttribute(bstrSubSystem, vt1);
					spElement->setAttribute(bstrGenerateDebugInformation, vtTrue);

					if (S_OK != spElement->getAttribute(bstrTargetMachine, &vt))
					{
						spElement->setAttribute(bstrTargetMachine, vt1);
					}

					spElement->removeAttribute(bstrModuleDefinitionFile);
					spElement->removeAttribute(bstrRegisterOutput);
				}
			}
		} else 
		{ //Source project is Library
			IXMLDOMDocumentPtr spDocument;
			IXMLDOMElementPtr spVCLinkerToolElement;
			CComBSTR bstrTool(L"Tool");
			CComBSTR bstrName(L"Name");
			CComVariant vtVCLinkerTool(L"VCLinkerTool");
			if (SUCCEEDED(spVisualStudioProject->selectNodes(CComBSTR(L"Configurations/Configuration"), &spNodeList)) && 
				SUCCEEDED(spNodeList->get_length(&nCount)) &&
				SUCCEEDED(spVisualStudioProject->get_ownerDocument(&spDocument)))
			{
				for(long n = 0; n < nCount; ++n)
				{
					if (SUCCEEDED(spNodeList->get_item(n, &spNode)) &&
						(spElement = spNode) &&
						SUCCEEDED(spDocument->createElement(bstrTool, &spVCLinkerToolElement)))
					{
						spVCLinkerToolElement->setAttribute(bstrName, vtVCLinkerTool);
						spVCLinkerToolElement->setAttribute(bstrSubSystem, vt1);
						spVCLinkerToolElement->setAttribute(bstrGenerateDebugInformation, vtTrue);
						spVCLinkerToolElement->setAttribute(bstrTargetMachine, vt1);
						spElement->appendChild(spVCLinkerToolElement, &spNode);
					}
				}
			}
		}
	}

	long nEn = 0;
	if (SUCCEEDED(spVisualStudioProject->selectNodes(CComBSTR(L"Configurations/Configuration"), &spNodeList)) && 
		SUCCEEDED(spNodeList->get_length(&nCount)))
	{
		CComVariant vt(1);
		CComBSTR bstrConfigurationType(L"ConfigurationType");
		for(long n = 0; n < nCount; ++n)
		{
			if (SUCCEEDED(spNodeList->get_item(n, &spNode)) &&
				(spElement = spNode) &&
				SUCCEEDED(spElement->setAttribute(bstrConfigurationType, vt)))
			{
				++nEn;
			}
		}
	}

	return nEn == nCount;
}

BOOL CProjectGenerator::ModifyPaths(IXMLDOMElementPtr & spVisualStudioProject)
{
	//existing project items should be now addressed from subdirectory
	IXMLDOMNodeListPtr spNodeList;
	IXMLDOMNodePtr spNode;
	IXMLDOMElementPtr spFile;
	long nCount = 0;
	
	if (SUCCEEDED(spVisualStudioProject->selectNodes(CComBSTR(L"Files//File[@RelativePath]"), &spNodeList)) &&
		SUCCEEDED(spNodeList->get_length(&nCount)))
	{
		CComVariant vt;
		CComBSTR bstrRelativePath(L"RelativePath");
		for(long n = 0; n < nCount; ++n)
		{
			if (SUCCEEDED(spNodeList->get_item(n, &spNode)) &&
				(spFile = spNode) &&
				(S_OK == spFile->getAttribute(bstrRelativePath, &vt)))
			{
				CAtlStringW wstr = vt.bstrVal[0] == (vt.bstrVal[0] == L'.') && (vt.bstrVal[1] == L'\\') ? L"." : L"..\\";
				wstr.Append(vt.bstrVal);
				if (FAILED(spFile->setAttribute(bstrRelativePath, CComVariant(wstr))))
					return FALSE;
			}
		}
	} 
	else
		return FALSE;

	return TRUE;
}

void CProjectGenerator::RemoveHlpFiles(IXMLDOMElementPtr & spVisualStudioProject)
{
	IXMLDOMNodeListPtr spNodeList;
	IXMLDOMNodePtr spNode;
	IXMLDOMElementPtr spFile;
	long nCount = 0;
	
	if (SUCCEEDED(spVisualStudioProject->selectNodes(CComBSTR(L"Files//File[@RelativePath]"), &spNodeList)) &&
		SUCCEEDED(spNodeList->get_length(&nCount)))
	{
		CComVariant vt;
		CComBSTR bstrRelativePath(L"RelativePath");
		for(long n = 0; n < nCount; ++n)
		{
			if (SUCCEEDED(spNodeList->get_item(n, &spNode)) &&
				(spFile = spNode) &&
				(S_OK == spFile->getAttribute(bstrRelativePath, &vt)))
			{
				CPathW path(vt.bstrVal);
				CAtlStringW wstrExt = path.GetExtension();
				wstrExt.MakeLower();
				if ((0 == wstrExt.Compare(L".hhp")) || (0 == wstrExt.Compare(L".hhc")) || (0 == wstrExt.Compare(L".hhk")))
				{
					IXMLDOMNodePtr spParent;
					if (SUCCEEDED(spNode->get_parentNode(&spParent)))
						spParent->removeChild(spNode, NULL);
				} 
				else
				{
					path.UnquoteSpaces();
					path.RemoveArgs();
					CAtlStringW wstrFileName = path.m_strPath.Mid(path.FindFileName());
					IXMLDOMNodePtr spFileConfiguration;
					
					if ((0 == wstrFileName.CompareNoCase(L"resource.h")) &&
						SUCCEEDED(spNode->selectSingleNode(CComBSTR(L"FileConfiguration"), &spFileConfiguration)) &&
						(spFileConfiguration))
					{
						spNode->removeChild(spFileConfiguration, NULL);
					}
				}
			}
		}
	}
}

void CProjectGenerator::RemovePostBuildEvents(IXMLDOMElementPtr & spVisualStudioProject)
{
	//Removing post build events eliminate COM-server registration
	IXMLDOMNodeListPtr spNodeList;
	IXMLDOMNodePtr spNode, spNodeParent;
	long nCount = 0;
	
	if (SUCCEEDED(spVisualStudioProject->selectNodes(CComBSTR(L"Configurations/Configuration/Tool[@Name=\"VCPostBuildEventTool\"]"), &spNodeList)) &&
		SUCCEEDED(spNodeList->get_length(&nCount)))
	{
		for(long n = 0; n < nCount; ++n)
		{
			if (SUCCEEDED(spNodeList->get_item(n, &spNode)) &&
				SUCCEEDED(spNode->get_parentNode(&spNodeParent)) &&
				(spNodeParent))
			{
				spNodeParent->removeChild(spNode, NULL);
			}
		}
	}
}
void CProjectGenerator::DefineOutputAndIntermediateDir(IXMLDOMElementPtr & spVisualStudioProject)
{
	IXMLDOMNodeListPtr spNodeList;
	IXMLDOMNodePtr spNode;
	IXMLDOMElementPtr spElement;
	long nCount = 0;
	
	if (SUCCEEDED(spVisualStudioProject->selectNodes(CComBSTR(L"Configurations/Configuration"), &spNodeList)) &&
		SUCCEEDED(spNodeList->get_length(&nCount)))
	{
		for(long n = 0; n < nCount; ++n)
		{
			if (SUCCEEDED(spNodeList->get_item(n, &spNode)) &&
				(spElement = spNode)
				)
			{
				spElement->setAttribute(CComBSTR(L"OutputDirectory"), CComVariant(L"$(ProjectDir)$(ConfigurationName)"));
				spElement->setAttribute(CComBSTR(L"IntermediateDirectory"), CComVariant(L"$(ProjectDir)$(ConfigurationName)"));
			}
		}
	}
}